package com.ghck.server.security;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Enumeration;
import java.util.TimeZone;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.ghck.server.exception.ErrorConstant;
import com.ghck.server.exception.GhckException;
import com.ghck.server.utils.MD5Tools;
import com.ghck.server.utils.SpringContextUtil;


public class AjkAuthenticationFilter implements  Filter {

	private final static String HEADER_AUTHORIZATION = "Authorization";
	private final static String AUTHENTICATION_PREFIX = "GHCK ";
	private final static String PARAM_NAME_ACCESS_ID = "accessId";
	private final static String PARAM_NAME_SIGNATURE = "signature";
	private static final String REALM_NAME = "GHCK";
	private static final String KEY="c9dddb12f2a24860";

	private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

	private Environment env;
	private UserDetailsService userDetailsService;
	private UserCache userCache;

	private Logger logger = LoggerFactory.getLogger(getClass());

	static {
		DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
	}

	static class SignData {

		private final String accessId;
		private final String signature;

		SignData(final String accessId,
				final String signature) {

			this.accessId = accessId;
			this.signature = signature;
		}

		String getAccessId() {

			return this.accessId;
		}

		String getSignature() {

			return this.signature;
		}
	}


	public Environment getEnv() {
		return env;
	}

	public void setEnv(Environment env) {
		this.env = env;
	}



	@Override
	public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)throws IOException,ServletException {
		String url =((HttpServletRequest) request).getRequestURI().replaceFirst(((HttpServletRequest) request).getContextPath(), "");
		Enumeration<String> headers = ((HttpServletRequest) request).getHeaderNames();
    	StringBuffer sb = new StringBuffer();
    	while (headers.hasMoreElements()) {
			String key = (String) headers.nextElement();
			sb.append(key).append("=").append( ((HttpServletRequest) request).getHeader(key)).append("\n");
    	}
    	
    	//logger.info("url:{},请求头:{}",url,sb.toString());
		if(env==null){
			env = SpringContextUtil.getBean(Environment.class);
		}
		if(userDetailsService==null){
			userDetailsService = SpringContextUtil.getBean("userDetailsService",UserDetailsService.class);
		}
		if(userCache==null){
			userCache = SpringContextUtil.getBean("userCache",UserCache.class);
		}

		if (!(request instanceof HttpServletRequest)) {
			chain.doFilter(request, response);
			return;
		}

		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;
		//httpResponse.addHeader("x-frame-options","SAMEORIGIN");
		httpResponse.setHeader("x-frame-options", "SAMEORIGIN");
		Boolean allowSuperAccessId = env.getProperty("cheat_sheet.allow_super_access_id", Boolean.class);

		SignData signData = getSignData(httpRequest);
		String superAccessId = request.getParameter("superAccessId");

		if (signData == null && (allowSuperAccessId == false || superAccessId == null)) {
			chain.doFilter(request, response);
			return;
		}
		AjkUserDetails user = null;
		AjkAuthenticationEntryPoint entryPoint = new AjkAuthenticationEntryPoint();
		entryPoint.setRealmName(REALM_NAME);
		if (signData != null) {
			try {
				user = getCurrentUserForFilter(signData.getAccessId());
				//如果用户未登录
				if(null == user){
					String path =((HttpServletRequest) request).getRequestURI().replaceFirst(((HttpServletRequest) request).getContextPath(), "");
					logger.info("token在系统中不存,作为匿名用户放行:{},token:{}", path,signData.getAccessId());
					chain.doFilter(httpRequest, httpResponse);
					return;
				}
			} catch (UsernameNotFoundException e) {
				entryPoint.commence(httpRequest, httpResponse,new AccountExpiredException("The access key has expired"));
				return;
			}
//			if (!validateSignature(httpRequest, signData, user)) {
//				entryPoint.commence(httpRequest, httpResponse, new BadSignatureException("The signature is not correct"));
//				return;
//			}
		} else if (superAccessId != null && allowSuperAccessId) {
			try {
				user = getCurrentUserForFilter(superAccessId);
				if(null == user){
					String path =((HttpServletRequest) request).getRequestURI().replaceFirst(((HttpServletRequest) request).getContextPath(), "");
					chain.doFilter(httpRequest, httpResponse);
					return;
				}
			} catch (GhckException e) {
				entryPoint.commence(httpRequest, httpResponse, new AccountExpiredException(ErrorConstant.ACCOUNT_EXPIRED.getMsg()));
				return;
			}
		}
		
		SecurityContextHolder.getContext().setAuthentication(createAuthentication(user));
		chain.doFilter(request, response);
	}
	
	private Authentication createAuthentication(AjkUserDetails user) {
		UsernamePasswordAuthenticationToken authRequest;
		authRequest = new UsernamePasswordAuthenticationToken(user.getUserId(),
				user.getPassword(),
				user.getAuthorities());
		authRequest.setDetails(user);
		return authRequest;
	}
	private boolean validateSignature(HttpServletRequest httpRequest, SignData signData, AjkUserDetails user)
			throws IOException, ServletException {
		try {
			String ts = httpRequest.getHeader("TS");
			String url = null2Empty(httpRequest.getRequestURI());
			StringBuilder sb = new StringBuilder();
			sb.append(user.getAccessKey()).append(url).append(ts).append(KEY);
			String signature = MD5Tools.MD5(sb.toString());
			signature = MD5Tools.MD5(signature);
			String encodedSignature = signData.getSignature();
			if (signature.equals(encodedSignature)) {
				return true;
			}
		} catch (Exception e) {
			logger.error("The authentication is caught: ", e);
			throw new AuthenticationServiceException("Internal server error");
		}
		return false;
	}

	public SignData getSignData(HttpServletRequest httpRequest)
			throws IOException, ServletException {

		String auth = httpRequest.getHeader(HEADER_AUTHORIZATION);
		SignData signData = extractSignData(auth);

		if (signData == null) {
			signData = extractSignData(httpRequest);
			if (signData == null) {
				return null;
			}
		}
		return signData;
	}


	public AjkUserDetails getCurrentUserForFilter(String accessId) throws UsernameNotFoundException {

		AjkUserDetails user = (AjkUserDetails)userCache.getUserFromCache(accessId);

		if (user == null) {
			user = (AjkUserDetails) userDetailsService.loadUserByUsername(accessId);
			if(null == user){
				return null;
			}
			userCache.putUserInCache(user);
		}
		return user;
	}

	/**
	 * Extract authentication info from authorization header
	 * @param authentication the authentication string
	 * @return the extracted data that needs verification
	 */
	private SignData extractSignData(String authentication) {

		if (authentication == null || !authentication.startsWith(AUTHENTICATION_PREFIX)) {
			return null;
		}
		authentication = authentication.substring(AUTHENTICATION_PREFIX.length());
		String[] signature = authentication.split(":");
		if (signature.length != 2) {
			return null;
		}
		return new SignData(signature[0],
				signature[1]);
	}
	public static void main(String[] args) {
		String authentication = "GHCK 97bc3d23-44c0-4517-adb4-b0429d69677e:123456";
		authentication = authentication.substring(AUTHENTICATION_PREFIX.length());
		String[] signature = authentication.split(":");
		System.out.println("|" + signature[0] + "|");
		System.out.println("|" + signature[1] + "|");
	}

	/**
	 * Extract authentication info from the body of the request
	 * @param request the http request
	 * @return the extracted data that needs verification
	 */
	private SignData extractSignData(HttpServletRequest request) {

		String accessId = request.getParameter(PARAM_NAME_ACCESS_ID);
		String signature = request.getParameter(PARAM_NAME_SIGNATURE);

		if (accessId == null || signature == null) {
			return null;
		}
		return new SignData(accessId, signature);
	}
	private String null2Empty(String str) {
		return str == null ? "" : str;
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {

	}

	@Override
	public void destroy() {

	}
}

